#include "CPUDisassembly.h"

#include <QClipboard>
#include <QDesktopServices>
#include <QFile>
#include <QFileDialog>
#include <QMessageBox>

#include "AssembleDialog.h"
#include "BreakpointMenu.h"
#include "Breakpoints.h"
#include "Bridge.h"
#include "BrowseDialog.h"
#include "CPUMultiDump.h"
#include "CPUSideBar.h"
#include "CPUWidget.h"
#include "Configuration.h"
#include "EncodeMap.h"
#include "GotoDialog.h"
#include "HexEditDialog.h"
#include "Imports.h"
#include "LineEditDialog.h"
#include "MemoryPage.h"
#include "MiscUtil.h"
#include "SourceViewerManager.h"
#include "StringUtil.h"
#include "WordEditDialog.h"
#include "XrefBrowseDialog.h"
#include "main.h"

#ifndef PlaySoundA
#define PlaySoundA(...)
#endif

CPUDisassembly::CPUDisassembly(QWidget* parent, bool isMain)
    : Disassembly(parent, isMain) {
  setWindowTitle("Disassembly");

  // Create the action list for the right click context menu
  setupRightClickContextMenu();

  // Connect bridge<->disasm calls
  connect(Bridge::getBridge(), SIGNAL(disassembleAt(dsint, dsint)), this,
          SLOT(disassembleAtSlot(dsint, dsint)));
  if (mIsMain) {
    connect(Bridge::getBridge(), SIGNAL(selectionDisasmGet(SELECTIONDATA*)),
            this, SLOT(selectionGetSlot(SELECTIONDATA*)));
    connect(Bridge::getBridge(),
            SIGNAL(selectionDisasmSet(const SELECTIONDATA*)), this,
            SLOT(selectionSetSlot(const SELECTIONDATA*)));
    connect(Bridge::getBridge(), SIGNAL(displayWarning(QString, QString)), this,
            SLOT(displayWarningSlot(QString, QString)));
  }

  // Connect some internal signals
  connect(this, SIGNAL(selectionExpanded()), this,
          SLOT(selectionUpdatedSlot()));

  Initialize();
}

void CPUDisassembly::mousePressEvent(QMouseEvent* event) {
  if (event->buttons() == Qt::MiddleButton)  // copy address to clipboard
  {
    if (!DbgIsDebugging()) return;
    MessageBeep(MB_OK);
    if (event->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier))
      copyRvaSlot();
    else
      copyAddressSlot();
  } else {
    mHighlightContextMenu = false;
    Disassembly::mousePressEvent(event);
    if (mHighlightingMode)  // disable highlighting mode after clicked
    {
      mHighlightContextMenu = true;
      mHighlightingMode = false;
      reloadData();
    }
  }
}

void CPUDisassembly::mouseDoubleClickEvent(QMouseEvent* event) {
  if (event->button() != Qt::LeftButton) return;
  switch (getColumnIndexFromX(event->x())) {
    case 0:  // address
    {
      dsint mSelectedVa = rvaToVa(getInitialSelection());
      if (mRvaDisplayEnabled && mSelectedVa == mRvaDisplayBase)
        mRvaDisplayEnabled = false;
      else {
        mRvaDisplayEnabled = true;
        mRvaDisplayBase = mSelectedVa;
        mRvaDisplayPageBase = getBase();
      }
      reloadData();
    } break;

    // (Opcodes) Set INT3 breakpoint
    case 1:
      mBreakpointMenu->toggleInt3BPActionSlot();
      break;

      // (Disassembly) Assemble dialog
      case 2: {
        auto found = mDstRvas.find(getInitialSelection());
        if (found != mDstRvas.end()) {
          gotoAddress(found->second);
        }
        break;
      }

    // (Comments) Set comment dialog
    case 3:
      setCommentSlot();
      break;

    // Undefined area
    default:
      Disassembly::mouseDoubleClickEvent(event);
      break;
  }
}

void CPUDisassembly::addFollowReferenceMenuItem(QString name, dsint value,
                                                QMenu* menu, bool isReferences,
                                                bool isFollowInCPU) {
  foreach (QAction* action, menu->actions())  // check for duplicate action
    if (action->text() == name) return;
  QAction* newAction = new QAction(name, this);
  newAction->setFont(font());
  menu->addAction(newAction);
  if (isFollowInCPU)
    newAction->setObjectName(QString("CPU|") + ToPtrString(value));
  else
    newAction->setObjectName(QString(isReferences ? "REF|" : "DUMP|") +
                             ToPtrString(value));

  connect(newAction, SIGNAL(triggered()), this, SLOT(followActionSlot()));
}

void CPUDisassembly::setupFollowReferenceMenu(dsint wVA, QMenu* menu,
                                              bool isReferences,
                                              bool isFollowInCPU) {
  // remove previous actions
  QList<QAction*> list = menu->actions();
  for (int i = 0; i < list.length(); i++) menu->removeAction(list.at(i));

  // most basic follow action
  if (!isFollowInCPU) {
    if (isReferences)
      menu->addAction(mReferenceSelectedAddressAction);
    else
      addFollowReferenceMenuItem(tr("&Selected Address"), wVA, menu,
                                 isReferences, isFollowInCPU);
  }

#if 0
  // add follow actions
  DISASM_INSTR instr;
  DbgDisasmAt(wVA, &instr);

  if (!isReferences)  // follow in dump
  {
    for (int i = 0; i < instr.argcount; i++) {
      const DISASM_ARG& arg = instr.arg[i];
      if (arg.type == arg_memory) {
        QString segment = "";
#ifdef _WIN64
        if (arg.segment == SEG_GS) segment = "gs:";
#else   // x32
        if (arg.segment == SEG_FS) segment = "fs:";
#endif  //_WIN64
        if (arg.value != arg.constant) {
          if (DbgMemIsValidReadPtr(arg.value))
            addFollowReferenceMenuItem(
                tr("&Address: ") + segment +
                    QString(arg.mnemonic).toUpper().trimmed(),
                arg.value, menu, isReferences, isFollowInCPU);
        }
        if (DbgMemIsValidReadPtr(arg.constant))
          addFollowReferenceMenuItem(
              tr("&Constant: ") + getSymbolicName(arg.constant), arg.constant,
              menu, isReferences, isFollowInCPU);
        if (DbgMemIsValidReadPtr(arg.memvalue)) {
          addFollowReferenceMenuItem(
              tr("&Value: ") + segment + "[" + QString(arg.mnemonic) + "]",
              arg.memvalue, menu, isReferences, isFollowInCPU);
          // Check for switch statement
          if (memcmp(instr.instruction, "jmp ", 4) == 0 &&
              DbgMemIsValidReadPtr(
                  arg.constant))  // todo: extend check for exact form "jmp
                                  // [reg*4+disp]"
          {
            duint* switchTable = new duint[512];
            memset(switchTable, 0, 512 * sizeof(duint));
            if (DbgMemRead(arg.constant, switchTable, 512 * sizeof(duint))) {
              int index;
              for (index = 0; index < 512; index++)
                if (!DbgFunctions()->MemIsCodePage(switchTable[index], false))
                  break;
              if (index >= 2 && index < 512)
                for (int index2 = 0; index2 < index; index2++)
                  addFollowReferenceMenuItem(
                      tr("Jump table%1: ").arg(index2) +
                          ToHexString(switchTable[index2]),
                      switchTable[index2], menu, isReferences, isFollowInCPU);
            }
            delete[] switchTable;
          }
        }

      } else  // arg_normal
      {
        if (DbgMemIsValidReadPtr(arg.value)) {
          QString symbolicName = getSymbolicName(arg.value);
          QString mnemonic = QString(arg.mnemonic).trimmed();
          if (mnemonic != ToHexString(arg.value))
            mnemonic = mnemonic + ": " + symbolicName;
          else
            mnemonic = symbolicName;
          addFollowReferenceMenuItem(mnemonic, arg.value, menu, isReferences,
                                     isFollowInCPU);
        }
      }
    }
  } else  // find references
  {
    for (int i = 0; i < instr.argcount; i++) {
      const DISASM_ARG arg = instr.arg[i];
      QString constant = ToHexString(arg.constant);
      if (DbgMemIsValidReadPtr(arg.constant))
        addFollowReferenceMenuItem(tr("Address: ") + constant, arg.constant,
                                   menu, isReferences, isFollowInCPU);
      else if (arg.constant)
        addFollowReferenceMenuItem(tr("Constant: ") + constant, arg.constant,
                                   menu, isReferences, isFollowInCPU);
    }
  }
#endif
}

/************************************************************************************
                            Mouse Management
************************************************************************************/
/**
 * @brief       This method has been reimplemented. It manages the richt click
 * context menu.
 *
 * @param[in]   event       Context menu event
 *
 * @return      Nothing.
 */
void CPUDisassembly::contextMenuEvent(QContextMenuEvent* event) {
  QMenu wMenu(this);
  if (!mHighlightContextMenu)
    mMenuBuilder->build(&wMenu);
  else if (mHighlightToken.text.length())
    mHighlightMenuBuilder->build(&wMenu);
  if (wMenu.actions().length()) wMenu.exec(event->globalPos());
}

/************************************************************************************
                         Context Menu Management
************************************************************************************/
void CPUDisassembly::setupRightClickContextMenu() {
  mMenuBuilder = new MenuBuilder(this, [](QMenu*) { return DbgIsDebugging(); });

  MenuBuilder* binaryMenu = new MenuBuilder(this);
  binaryMenu->addAction(makeShortcutAction(DIcon("binary_edit.png"),
                                           tr("&Edit"), SLOT(binaryEditSlot()),
                                           "ActionBinaryEdit"));
  binaryMenu->addAction(
      makeShortcutAction(DIcon("binary_fill.png"), tr("&Fill..."),
                         SLOT(binaryFillSlot()), "ActionBinaryFill"));
  binaryMenu->addAction(
      makeShortcutAction(DIcon("binary_fill_nop.png"), tr("Fill with &NOPs"),
                         SLOT(binaryFillNopsSlot()), "ActionBinaryFillNops"));
  binaryMenu->addSeparator();
  binaryMenu->addAction(makeShortcutAction(DIcon("binary_copy.png"),
                                           tr("&Copy"), SLOT(binaryCopySlot()),
                                           "ActionBinaryCopy"));
  binaryMenu->addAction(
      makeShortcutAction(DIcon("binary_paste.png"), tr("&Paste"),
                         SLOT(binaryPasteSlot()), "ActionBinaryPaste"),
      [](QMenu*) { return QApplication::clipboard()->mimeData()->hasText(); });
  binaryMenu->addAction(
      makeShortcutAction(
          DIcon("binary_paste_ignoresize.png"), tr("Paste (&Ignore Size)"),
          SLOT(binaryPasteIgnoreSizeSlot()), "ActionBinaryPasteIgnoreSize"),
      [](QMenu*) { return QApplication::clipboard()->mimeData()->hasText(); });
  mMenuBuilder->addMenu(makeMenu(DIcon("binary.png"), tr("&Binary")),
                        binaryMenu);

  MenuBuilder* copyMenu = new MenuBuilder(this);
  copyMenu->addAction(
      makeShortcutAction(DIcon("copy_selection.png"), tr("&Selection"),
                         SLOT(copySelectionSlot()), "ActionCopy"));
  copyMenu->addAction(makeAction(DIcon("copy_selection.png"),
                                 tr("Selection to &File"),
                                 SLOT(copySelectionToFileSlot())));
  copyMenu->addAction(makeAction(DIcon("copy_selection_no_bytes.png"),
                                 tr("Selection (&No Bytes)"),
                                 SLOT(copySelectionNoBytesSlot())));
  copyMenu->addAction(makeAction(DIcon("copy_selection_no_bytes.png"),
                                 tr("Selection to File (No Bytes)"),
                                 SLOT(copySelectionToFileNoBytesSlot())));
  copyMenu->addAction(
      makeShortcutAction(DIcon("copy_address.png"), tr("&Address"),
                         SLOT(copyAddressSlot()), "ActionCopyAddress"));
  copyMenu->addAction(makeAction(DIcon("copy_address.png"), tr("File Address"),
                                 SLOT(copyFileAddressSlot())));
  copyMenu->addAction(makeShortcutAction(DIcon("copy_address.png"), tr("&RVA"),
                                         SLOT(copyRvaSlot()), "ActionCopyRva"));
  copyMenu->addAction(
      makeShortcutAction(DIcon("fileoffset.png"), tr("&File Offset"),
                         SLOT(copyFileOffsetSlot()), "ActionCopyFileOffset"));
  copyMenu->addAction(makeAction(tr("&Header VA"), SLOT(copyHeaderVaSlot())));
  copyMenu->addAction(makeAction(DIcon("copy_disassembly.png"),
                                 tr("Disassembly"),
                                 SLOT(copyDisassemblySlot())));
  copyMenu->addBuilder(new MenuBuilder(this, [this](QMenu* menu) {
    QSet<QString> labels;
    if (!getLabelsFromInstruction(rvaToVa(getInitialSelection()), labels))
      return false;
    menu->addSeparator();
    for (const auto& label : labels)
      menu->addAction(makeAction(label, SLOT(labelCopySlot())));
    return true;
  }));
  mMenuBuilder->addMenu(makeMenu(DIcon("copy.png"), tr("&Copy")), copyMenu);

  mMenuBuilder->addAction(
      makeShortcutAction(DIcon("eraser.png"), tr("&Restore selection"),
                         SLOT(undoSelectionSlot()), "ActionUndoSelection"),
      [this](QMenu*) {
        dsint start = rvaToVa(getSelectionStart());
        dsint end = rvaToVa(getSelectionEnd());
        return DbgFunctions()->PatchInRange(start,
                                            end);  // something
                                                   // patched in selected range
      });

  mBreakpointMenu = new BreakpointMenu(this, getActionHelperFuncs(), [this]() {
    return rvaToVa(getInitialSelection());
  });
  mBreakpointMenu->build(mMenuBuilder);

  mMenuBuilder->addMenu(
      makeMenu(DIcon("dump.png"), tr("&Follow in Dump")), [this](QMenu* menu) {
        setupFollowReferenceMenu(rvaToVa(getInitialSelection()), menu, false,
                                 false);
        return true;
      });

  mMenuBuilder->addMenu(
      makeMenu(DIcon("processor-cpu.png"), tr("&Follow in Disassembler")),
      [this](QMenu* menu) {
        setupFollowReferenceMenu(rvaToVa(getInitialSelection()), menu, false,
                                 true);
        return menu->actions().length() !=
               0;  // only add this menu if there is something to follow
      });

#if 0
  mMenuBuilder->addAction(makeShortcutAction(
      DIcon("memmap_find_address_page.png"), tr("Follow in Memory Map"),
      SLOT(followInMemoryMapSlot()), "ActionFollowMemMap"));

  mMenuBuilder->addAction(
      makeShortcutAction(DIcon("source.png"), tr("Open Source File"),
                         SLOT(openSourceSlot()), "ActionOpenSourceFile"),
      [this](QMenu*) {
        return false;  // DbgFunctions()->GetSourceFromAddr(rvaToVa(getInitialSelection()),
                       // 0, 0);
      });

  mMenuBuilder->addAction(makeShortcutAction(DIcon("graph.png"), tr("Graph"),
                                             SLOT(graphSlot()), "ActionGraph"));

  mMenuBuilder->addMenu(
      makeMenu(DIcon("help.png"), tr("Help on Symbolic Name")),
      [this](QMenu* menu) {
        QSet<QString> labels;
        if (!getLabelsFromInstruction(rvaToVa(getInitialSelection()), labels))
          return false;
        for (auto label : labels)
          menu->addAction(makeAction(label, SLOT(labelHelpSlot())));
        return true;
      });
  mMenuBuilder->addAction(
      makeShortcutAction(DIcon("helpmnemonic.png"), tr("Help on mnemonic"),
                         SLOT(mnemonicHelpSlot()), "ActionHelpOnMnemonic"));
  QAction* mnemonicBrief = makeShortcutAction(
      DIcon("helpbrief.png"), tr("Show mnemonic brief"),
      SLOT(mnemonicBriefSlot()), "ActionToggleMnemonicBrief");
  mMenuBuilder->addAction(mnemonicBrief, [this, mnemonicBrief](QMenu*) {
    if (mShowMnemonicBrief)
      mnemonicBrief->setText(tr("Hide mnemonic brief"));
    else
      mnemonicBrief->setText(tr("Show mnemonic brief"));
    return true;
  });
#endif

  mMenuBuilder->addAction(makeShortcutAction(
      DIcon("highlight.png"), tr("&Highlighting mode"),
      SLOT(enableHighlightingModeSlot()), "ActionHighlightingMode"));

#if 0
  MenuBuilder* labelMenu = new MenuBuilder(this);
  labelMenu->addAction(makeShortcutAction(
      tr("Label Current Address"), SLOT(setLabelSlot()), "ActionSetLabel"));
  QAction* labelAddress = makeShortcutAction(
      tr("Label"), SLOT(setLabelAddressSlot()), "ActionSetLabelOperand");

  labelMenu->addAction(labelAddress, [this, labelAddress](QMenu*) {
    BASIC_INSTRUCTION_INFO instr_info;
    DbgDisasmFastAt(rvaToVa(getInitialSelection()), &instr_info);

    duint addr;
    if (instr_info.type & TYPE_MEMORY)
      addr = instr_info.memory.value;
    else if (instr_info.type & TYPE_ADDR)
      addr = instr_info.addr;
    else if (instr_info.type & TYPE_VALUE)
      addr = instr_info.value.value;
    else
      return false;

    labelAddress->setText(tr("Label") + " " + ToPtrString(addr));

    return DbgMemIsValidReadPtr(addr);
  });
  mMenuBuilder->addMenu(makeMenu(DIcon("label.png"), tr("Label")), labelMenu);

  QAction* traceRecordDisable =
      makeAction(DIcon("close-all-tabs.png"), tr("Disable"),
                 SLOT(ActionTraceRecordDisableSlot()));
  QAction* traceRecordEnableBit =
      makeAction(DIcon("bit.png"), tr("Bit"), SLOT(ActionTraceRecordBitSlot()));
  QAction* traceRecordEnableByte = makeAction(
      DIcon("byte.png"), tr("Byte"), SLOT(ActionTraceRecordByteSlot()));
  QAction* traceRecordEnableWord = makeAction(
      DIcon("word.png"), tr("Word"), SLOT(ActionTraceRecordWordSlot()));
  QAction* traceRecordToggleRunTrace = makeShortcutAction(
      tr("Start Run Trace"), SLOT(ActionTraceRecordToggleRunTraceSlot()),
      "ActionToggleRunTrace");
  mMenuBuilder->addMenu(
      makeMenu(DIcon("trace.png"), tr("Trace record")), [=](QMenu* menu) {
        if (DbgFunctions()->GetTraceRecordType(rvaToVa(
                getInitialSelection())) == TRACERECORDTYPE::TraceRecordNone) {
          menu->addAction(traceRecordEnableBit);
          menu->addAction(traceRecordEnableByte);
          menu->addAction(traceRecordEnableWord);
        } else
          menu->addAction(traceRecordDisable);
        menu->addSeparator();
        if (DbgValFromString("tr.runtraceenabled()") == 1)
          traceRecordToggleRunTrace->setText(tr("Stop Run Trace"));
        else
          traceRecordToggleRunTrace->setText(tr("Start Run Trace"));
        menu->addAction(traceRecordToggleRunTrace);
        return true;
      });
#endif

  mMenuBuilder->addAction(
      makeShortcutAction(DIcon("comment.png"), tr("Comment"),
                         SLOT(setCommentSlot()), "ActionSetComment"));
  mMenuBuilder->addAction(
      makeShortcutAction(DIcon("bookmark_toggle.png"), tr("Toggle Bookmark"),
                         SLOT(setBookmarkSlot()), "ActionToggleBookmark"));
  mMenuBuilder->addSeparator();

  MenuBuilder* analysisMenu = new MenuBuilder(this);
  analysisMenu->addAction(
      makeShortcutAction(DIcon("analyzemodule.png"), tr("Re-analyze module"),
                         SLOT(analyzeModuleSlot()), "ActionAnalyzeModule"));
#if 0
  QAction* toggleFunctionAction =
      makeShortcutAction(DIcon("functions.png"), tr("Function"),
                         SLOT(toggleFunctionSlot()), "ActionToggleFunction");
  analysisMenu->addAction(
      toggleFunctionAction, [this, toggleFunctionAction](QMenu*) {
        if (!DbgFunctionOverlaps(rvaToVa(getSelectionStart()),
                                 rvaToVa(getSelectionEnd())))
          toggleFunctionAction->setText(tr("Add function"));
        else
          toggleFunctionAction->setText(tr("Delete function"));
        return true;
      });
  QAction* toggleArgumentAction =
      makeShortcutAction(DIcon("arguments.png"), tr("Argument"),
                         SLOT(toggleArgumentSlot()), "ActionToggleArgument");
  analysisMenu->addAction(
      toggleArgumentAction, [this, toggleArgumentAction](QMenu*) {
        if (!DbgArgumentOverlaps(rvaToVa(getSelectionStart()),
                                 rvaToVa(getSelectionEnd())))
          toggleArgumentAction->setText(tr("Add argument"));
        else
          toggleArgumentAction->setText(tr("Delete argument"));
        return true;
      });
  analysisMenu->addAction(
      makeShortcutAction(tr("Add loop"), SLOT(addLoopSlot()), "ActionAddLoop"));
  analysisMenu->addAction(
      makeShortcutAction(tr("Delete loop"), SLOT(deleteLoopSlot()),
                         "ActionDeleteLoop"),
      [this](QMenu*) {
        return findDeepestLoopDepth(rvaToVa(getSelectionStart())) >= 0;
      });

  analysisMenu->addAction(makeShortcutAction(
      DIcon("remove_analysis_from_module.png"),
      tr("Remove type analysis from module"), SLOT(removeAnalysisModuleSlot()),
      "ActionRemoveTypeAnalysisFromModule"));
  analysisMenu->addAction(
      makeShortcutAction(DIcon("remove_analysis_from_selection.png"),
                         tr("Remove type analysis from selection"),
                         SLOT(removeAnalysisSelectionSlot()),
                         "ActionRemoveTypeAnalysisFromSelection"));
  analysisMenu->addSeparator();

  QMenu* encodeTypeMenu = makeMenu(DIcon("treat_selection_head_as.png"),
                                   tr("Treat selection &head as"));
  QMenu* encodeTypeRangeMenu = makeMenu(DIcon("treat_from_selection_as.png"),
                                        tr("Treat from &selection as"));

  const char* strTable[] = {
      "Code",    "Byte",  "Word",   "Dword",   "Fword",       "Qword", "Tbyte",
      "Oword",   nullptr, "Float",  "Double",  "Long Double", nullptr, "ASCII",
      "UNICODE", nullptr, "MMWord", "XMMWord", "YMMWord"};

  const char* shortcutTable[] = {
      "Code",    "Byte",  "Word",   "Dword",   "Fword",      "Qword", "Tbyte",
      "Oword",   nullptr, "Float",  "Double",  "LongDouble", nullptr, "ASCII",
      "UNICODE", nullptr, "MMWord", "XMMWord", "YMMWord"};

  const char* iconTable[] = {
      "cmd",     "byte",  "word",   "dword",  "fword",      "qword", "tbyte",
      "oword",   nullptr, "float",  "double", "longdouble", nullptr, "ascii",
      "unicode", nullptr, "mmword", "xmm",    "ymm"};

  ENCODETYPE enctypeTable[] = {enc_code,   enc_byte,    enc_word,    enc_dword,
                               enc_fword,  enc_qword,   enc_tbyte,   enc_oword,
                               enc_middle, enc_real4,   enc_real8,   enc_real10,
                               enc_middle, enc_ascii,   enc_unicode, enc_middle,
                               enc_mmword, enc_xmmword, enc_ymmword};

  int enctypesize = sizeof(enctypeTable) / sizeof(ENCODETYPE);

  for (int i = 0; i < enctypesize; i++) {
    if (enctypeTable[i] == enc_middle) {
      encodeTypeRangeMenu->addSeparator();
      encodeTypeMenu->addSeparator();
    } else {
      QAction* action;
      QIcon icon;
      if (iconTable[i])
        icon = DIcon(QString("treat_selection_as_%1.png").arg(iconTable[i]));
      if (shortcutTable[i])
        action = makeShortcutAction(icon, tr(strTable[i]),
                                    SLOT(setEncodeTypeRangeSlot()),
                                    QString("ActionTreatSelectionAs%1")
                                        .arg(shortcutTable[i])
                                        .toUtf8()
                                        .constData());
      else
        action =
            makeAction(icon, tr(strTable[i]), SLOT(setEncodeTypeRangeSlot()));
      action->setData(enctypeTable[i]);
      encodeTypeRangeMenu->addAction(action);
      if (shortcutTable[i])
        action =
            makeShortcutAction(icon, tr(strTable[i]), SLOT(setEncodeTypeSlot()),
                               QString("ActionTreatSelectionHeadAs%1")
                                   .arg(shortcutTable[i])
                                   .toUtf8()
                                   .constData());
      else
        action = makeAction(icon, tr(strTable[i]), SLOT(setEncodeTypeSlot()));
      action->setData(enctypeTable[i]);
      encodeTypeMenu->addAction(action);
    }
  }

  analysisMenu->addMenu(encodeTypeRangeMenu);
  analysisMenu->addMenu(encodeTypeMenu);

  mMenuBuilder->addAction(
      makeShortcutAction(
          DIcon("pdb.png"), tr("Download Symbols for This Module"),
          SLOT(downloadCurrentSymbolsSlot()), "ActionDownloadSymbol"),
      [this](QMenu*) {
        // only show this action in system modules (generally user modules don't
        // have downloadable symbols)
        return DbgFunctions()->ModGetParty(rvaToVa(getInitialSelection())) == 1;
      });
#endif
  mMenuBuilder->addMenu(makeMenu(DIcon("analysis.png"), tr("Analysis")),
                        analysisMenu);
  mMenuBuilder->addAction(
      makeShortcutAction(DIcon("memmap_alloc_memory.png"), tr("Create Page Module"),
                         SLOT(createPageModuleSlot()), "CreatePageModule"));
  mMenuBuilder->addSeparator();

  mMenuBuilder->addAction(
      makeShortcutAction(DIcon("compile.png"), tr("Assemble"),
                         SLOT(assembleSlot()), "ActionAssemble"));
  mMenuBuilder->addAction(
      makeShortcutAction(DIcon("compile-warning.png"), tr("Decompile"),
                         SLOT(decompileSlot()), "ActionDecompile"));
  mMenuBuilder->addAction(
      makeShortcutAction(DIcon("source.png"), tr("Decompile Module"),
                         SLOT(decompileModuleSlot()), "ActionDecompileModule"));
  removeAction(mMenuBuilder->addAction(makeShortcutAction(
      DIcon("patch.png"), tr("Patches"), SLOT(showPatchesSlot()),
      "ViewPatches")));  // prevent conflicting shortcut with the MainWindow
  mMenuBuilder->addSeparator();

#if 0
  mMenuBuilder->addAction(makeShortcutAction(
      DIcon("neworigin.png"), tr("Set New Origin Here"),
      SLOT(setNewOriginHereActionSlot()), "ActionSetNewOriginHere"));
  mMenuBuilder->addAction(makeShortcutAction(
      DIcon("createthread.png"), tr("Create New Thread Here"),
      SLOT(createThreadSlot()), "ActionCreateNewThreadHere"));
#endif

  MenuBuilder* gotoMenu = new MenuBuilder(this);
  gotoMenu->addAction(makeShortcutAction(DIcon("cbp.png"), tr("Origin"),
                                         SLOT(gotoOriginSlot()),
                                         "ActionGotoOrigin"));
  gotoMenu->addAction(
      makeShortcutAction(DIcon("previous.png"), tr("Previous"),
                         SLOT(gotoPreviousSlot()), "ActionGotoPrevious"),
      [this](QMenu*) { return historyHasPrevious(); });
  gotoMenu->addAction(
      makeShortcutAction(DIcon("next.png"), tr("Next"), SLOT(gotoNextSlot()),
                         "ActionGotoNext"),
      [this](QMenu*) { return historyHasNext(); });
  gotoMenu->addAction(
      makeShortcutAction(DIcon("geolocation-goto.png"), tr("Expression"),
                         SLOT(gotoExpressionSlot()), "ActionGotoExpression"));
  gotoMenu->addAction(
      makeShortcutAction(DIcon("fileoffset.png"), tr("File Offset"),
                         SLOT(gotoFileOffsetSlot()), "ActionGotoFileOffset"),
      [this](QMenu*) {
        char modname[MAX_MODULE_SIZE] = "";
        return DbgGetModuleAt(rvaToVa(getInitialSelection()), modname);
      });
  gotoMenu->addAction(makeShortcutAction(DIcon("top.png"), tr("Start of Page"),
                                         SLOT(gotoStartSlot()),
                                         "ActionGotoStart"));
  gotoMenu->addAction(makeShortcutAction(DIcon("bottom.png"), tr("End of Page"),
                                         SLOT(gotoEndSlot()), "ActionGotoEnd"));
  gotoMenu->addAction(
      makeShortcutAction(DIcon("functionstart.png"), tr("Start of Function"),
                         SLOT(gotoFunctionStartSlot()),
                         "ActionGotoFunctionStart"),
      [this](QMenu*) {
        return DbgFunctionGet(rvaToVa(getInitialSelection()), nullptr, nullptr);
      });
  gotoMenu->addAction(
      makeShortcutAction(DIcon("functionend.png"), tr("End of Function"),
                         SLOT(gotoFunctionEndSlot()), "ActionGotoFunctionEnd"),
      [this](QMenu*) {
        return DbgFunctionGet(rvaToVa(getInitialSelection()), nullptr, nullptr);
      });

#if 0
  gotoMenu->addAction(
      makeShortcutAction(DIcon("prevref.png"), tr("Previous Reference"),
                         SLOT(gotoPreviousReferenceSlot()),
                         "ActionGotoPreviousReference"),
      [](QMenu*) {
        return !!DbgEval(
            "refsearch.count() && ($__disasm_refindex > 0 || dis.sel() != "
            "refsearch.addr($__disasm_refindex))");
      });
  gotoMenu->addAction(
      makeShortcutAction(DIcon("nextref.png"), tr("Next Reference"),
                         SLOT(gotoNextReferenceSlot()),
                         "ActionGotoNextReference"),
      [](QMenu*) {
        return !!DbgEval(
            "refsearch.count() && ($__disasm_refindex < refsearch.count()|| "
            "dis.sel() != refsearch.addr($__disasm_refindex))");
      });
#endif

  mMenuBuilder->addMenu(makeMenu(DIcon("goto.png"), tr("Go to")), gotoMenu);
  mMenuBuilder->addSeparator();
  mMenuBuilder->addAction(
      makeShortcutAction(DIcon("xrefs.png"), tr("xrefs..."),
                         SLOT(gotoXrefSlot()), "ActionXrefs"),
      [this](QMenu*) { return mXrefInfo.refcount > 0; });

  MenuBuilder* searchMenu = new MenuBuilder(this);
  MenuBuilder* mSearchRegionMenu = new MenuBuilder(this);
  MenuBuilder* mSearchModuleMenu = new MenuBuilder(this, [this](QMenu*) {
    return DbgFunctions()->ModBaseFromAddr(rvaToVa(getInitialSelection())) != 0;
  });
  MenuBuilder* mSearchFunctionMenu = new MenuBuilder(this, [this](QMenu*) {
    duint start, end;
    return DbgFunctionGet(rvaToVa(getInitialSelection()), &start, &end);
  });
  MenuBuilder* mSearchAllMenu = new MenuBuilder(this);

  // Search in Current Region menu
  mFindCommandRegion =
      makeShortcutAction(DIcon("search_for_command.png"), tr("C&ommand"),
                         SLOT(findCommandSlot()), "ActionFind");
#if 0
  mFindConstantRegion = makeAction(DIcon("search_for_constant.png"),
                                   tr("&Constant"), SLOT(findConstantSlot()));
  mFindStringsRegion =
      makeAction(DIcon("search_for_string.png"), tr("&String references"),
                 SLOT(findStringsSlot()));
#endif
  mFindCallsRegion = makeAction(DIcon("call.png"), tr("&Intermodular calls"),
                                SLOT(findCallsSlot()));
  mFindPatternRegion =
      makeShortcutAction(DIcon("search_for_pattern.png"), tr("&Pattern"),
                         SLOT(findPatternSlot()), "ActionFindPattern");
#if 0
  mFindGUIDRegion =
      makeAction(DIcon("guid.png"), tr("&GUID"), SLOT(findGUIDSlot()));
#endif
  mSearchRegionMenu->addAction(mFindCommandRegion);
  //mSearchRegionMenu->addAction(mFindConstantRegion);
  //mSearchRegionMenu->addAction(mFindStringsRegion);
  mSearchRegionMenu->addAction(mFindCallsRegion);
  mSearchRegionMenu->addAction(mFindPatternRegion);
  //mSearchRegionMenu->addAction(mFindGUIDRegion);

  // Search in Current Module menu
#if 0
  mFindCommandModule =
      makeShortcutAction(DIcon("search_for_command.png"), tr("C&ommand"),
                         SLOT(findCommandSlot()), "ActionFindInModule");
  mFindConstantModule = makeAction(DIcon("search_for_constant.png"),
                                   tr("&Constant"), SLOT(findConstantSlot()));
#endif
  mFindStringsModule =
      makeAction(DIcon("search_for_string.png"), tr("&String references"),
                 SLOT(findStringsSlot()));
#if 0
  mFindCallsModule = makeAction(DIcon("call.png"), tr("&Intermodular calls"),
                                SLOT(findCallsSlot()));
#endif
  mFindPatternModule =
      makeShortcutAction(DIcon("search_for_pattern.png"), tr("&Pattern"),
                         SLOT(findPatternSlot()), "ActionFindPatternInModule");
#if 0
  mFindGUIDModule =
      makeAction(DIcon("guid.png"), tr("&GUID"), SLOT(findGUIDSlot()));
#endif
  mFindNamesModule =
      makeShortcutAction(DIcon("names.png"), tr("&Names"),
                         SLOT(findNamesSlot()), "ActionFindNamesInModule");
  //mSearchModuleMenu->addAction(mFindCommandModule);
  //mSearchModuleMenu->addAction(mFindConstantModule);
  mSearchModuleMenu->addAction(mFindStringsModule);
  //mSearchModuleMenu->addAction(mFindCallsModule);
  mSearchModuleMenu->addAction(mFindPatternModule);
  //mSearchModuleMenu->addAction(mFindGUIDModule);
  mSearchModuleMenu->addAction(mFindNamesModule);

  // Search in Current Function menu
  mFindCommandFunction = makeAction(DIcon("search_for_command.png"),
                                    tr("C&ommand"), SLOT(findCommandSlot()));
#if 0
  mFindConstantFunction = makeAction(DIcon("search_for_constant.png"),
                                     tr("&Constant"), SLOT(findConstantSlot()));
  mFindStringsFunction =
      makeAction(DIcon("search_for_string.png"), tr("&String references"),
                 SLOT(findStringsSlot()));
#endif
  mFindCallsFunction = makeAction(DIcon("call.png"), tr("&Intermodular calls"),
                                  SLOT(findCallsSlot()));
  mFindPatternFunction = makeAction(DIcon("search_for_pattern.png"),
                                    tr("&Pattern"), SLOT(findPatternSlot()));
#if 0
  mFindGUIDFunction =
      makeAction(DIcon("guid.png"), tr("&GUID"), SLOT(findGUIDSlot()));
#endif
  mSearchFunctionMenu->addAction(mFindCommandFunction);
  //mSearchFunctionMenu->addAction(mFindConstantFunction);
  //mSearchFunctionMenu->addAction(mFindStringsFunction);
  mSearchFunctionMenu->addAction(mFindCallsFunction);
  mSearchFunctionMenu->addAction(mFindPatternFunction);
  //mSearchFunctionMenu->addAction(mFindGUIDFunction);

#if 0
  // Search in All Modules menu
  mFindCommandAll = makeAction(DIcon("search_for_command.png"), tr("C&ommand"),
                               SLOT(findCommandSlot()));
  mFindConstantAll = makeAction(DIcon("search_for_constant.png"),
                                tr("&Constant"), SLOT(findConstantSlot()));
  mFindStringsAll =
      makeAction(DIcon("search_for_string.png"), tr("&String references"),
                 SLOT(findStringsSlot()));
  mFindCallsAll = makeAction(DIcon("call.png"), tr("&Intermodular calls"),
                             SLOT(findCallsSlot()));
  mFindPatternAll = makeAction(DIcon("search_for_pattern.png"), tr("&Pattern"),
                               SLOT(findPatternSlot()));
  mFindGUIDAll =
      makeAction(DIcon("guid.png"), tr("&GUID"), SLOT(findGUIDSlot()));
  mSearchAllMenu->addAction(mFindCommandAll);
  mSearchAllMenu->addAction(mFindConstantAll);
  mSearchAllMenu->addAction(mFindStringsAll);
  mSearchAllMenu->addAction(mFindCallsAll);
  mSearchAllMenu->addAction(mFindPatternAll);
  mSearchAllMenu->addAction(mFindGUIDAll);
#endif

  searchMenu->addMenu(
      makeMenu(DIcon("search.png"), tr("Current Function")),
      mSearchFunctionMenu);
  searchMenu->addMenu(
      makeMenu(DIcon("search_current_region.png"), tr("Whole Section")),
      mSearchRegionMenu);
  searchMenu->addMenu(
      makeMenu(DIcon("search_current_module.png"), tr("Whole Module")),
      mSearchModuleMenu);
#if 0
  searchMenu->addMenu(
      makeMenu(DIcon("search_all_modules.png"), tr("All Modules")),
      mSearchAllMenu);
#endif
  mMenuBuilder->addMenu(makeMenu(DIcon("search-for.png"), tr("&Search for")),
                        searchMenu);

#if 0
  mReferenceSelectedAddressAction = makeShortcutAction(
      tr("&Selected Address(es)"), SLOT(findReferencesSlot()),
      "ActionFindReferencesToSelectedAddress");
  mReferenceSelectedAddressAction->setFont(QFont("Courier New", 8));

  mMenuBuilder->addMenu(makeMenu(DIcon("find.png"), tr("Find &references to")),
                        [this](QMenu* menu) {
                          setupFollowReferenceMenu(
                              rvaToVa(getInitialSelection()), menu, true,
                              false);
                          return true;
                        });
#endif

  // Plugins
  if (mIsMain) {
    mPluginMenu = new QMenu(this);
    Bridge::getBridge()->emitMenuAddToList(this, mPluginMenu, GUI_DISASM_MENU);
  }

  mMenuBuilder->addSeparator();
  mMenuBuilder->addBuilder(new MenuBuilder(this, [this](QMenu* menu) {
    DbgMenuPrepare(GUI_DISASM_MENU);
    if (mIsMain) {
      menu->addActions(mPluginMenu->actions());
    }
    return true;
  }));

  // Highlight menu
  mHighlightMenuBuilder = new MenuBuilder(this);

  mHighlightMenuBuilder->addAction(makeAction(
      DIcon("copy.png"), tr("Copy token &text"), SLOT(copyTokenTextSlot())));
  mHighlightMenuBuilder->addAction(
      makeAction(DIcon("copy_address.png"), tr("Copy token &value"),
                 SLOT(copyTokenValueSlot())),
      [this](QMenu*) {
        QString text;
        if (!getTokenValueText(text)) return false;
        return text != mHighlightToken.text;
      });

  mMenuBuilder->addSeparator();
  mMenuBuilder->addAction(
      makeAction(DIcon("hex.png"), tr("&Refresh"), SLOT(refreshPage())));
  mMenuBuilder->loadFromConfig();
}

void CPUDisassembly::gotoOriginSlot() {
  if (!DbgIsDebugging()) return;
  gotoAddress(DbgValFromString("cip"));
}

void CPUDisassembly::setNewOriginHereActionSlot() {
  if (!DbgIsDebugging()) return;
  duint wVA = rvaToVa(getInitialSelection());
  if (DbgFunctions()->IsDepEnabled() &&
      !DbgFunctions()->MemIsCodePage(wVA, false)) {
    QMessageBox msg(QMessageBox::Warning,
                    tr("Current address is not executable"),
                    tr("Setting new origin here may result in crash. Do you "
                       "really want to continue?"),
                    QMessageBox::Yes | QMessageBox::No, this);
    msg.setWindowIcon(DIcon("compile-warning.png"));
    msg.setParent(this, Qt::Dialog);
    msg.setWindowFlags(msg.windowFlags() & (~Qt::WindowContextHelpButtonHint));
    if (msg.exec() == QMessageBox::No) return;
  }
  QString wCmd = "cip=" + ToPtrString(wVA);
  DbgCmdExec(wCmd);
}

void CPUDisassembly::setLabelSlot() {
  if (!DbgIsDebugging()) return;
  duint wVA = rvaToVa(getInitialSelection());
  LineEditDialog mLineEdit(this);
  mLineEdit.setTextMaxLength(MAX_LABEL_SIZE - 2);
  QString addr_text = ToPtrString(wVA);
  char label_text[MAX_COMMENT_SIZE] = "";
  if (DbgGetLabelAt((duint)wVA, SEG_DEFAULT, label_text))
    mLineEdit.setText(QString(label_text));
  mLineEdit.setWindowTitle(tr("Add label at ") + addr_text);
restart:
  if (mLineEdit.exec() != QDialog::Accepted) return;
  QByteArray utf8data = mLineEdit.editText.toUtf8();
  if (!utf8data.isEmpty() && DbgIsValidExpression(utf8data.constData()) &&
      DbgValFromString(utf8data.constData()) != wVA) {
    QMessageBox msg(QMessageBox::Warning, tr("The label may be in use"),
                    tr("The label \"%1\" may be an existing label or a valid "
                       "expression. Using such label might have undesired "
                       "effects. Do you still want to continue?")
                        .arg(mLineEdit.editText),
                    QMessageBox::Yes | QMessageBox::No, this);
    msg.setWindowIcon(DIcon("compile-warning.png"));
    msg.setParent(this, Qt::Dialog);
    msg.setWindowFlags(msg.windowFlags() & (~Qt::WindowContextHelpButtonHint));
    if (msg.exec() == QMessageBox::No) goto restart;
  }
  if (!DbgSetLabelAt(wVA, utf8data.constData()))
    SimpleErrorBox(this, tr("Error!"), tr("DbgSetLabelAt failed!"));

  GuiUpdateAllViews();
}

void CPUDisassembly::setLabelAddressSlot() {
  if (!DbgIsDebugging()) return;
  BASIC_INSTRUCTION_INFO instr_info;
  DbgDisasmFastAt(rvaToVa(getInitialSelection()), &instr_info);

  duint addr;
  if (instr_info.type & TYPE_MEMORY)
    addr = instr_info.memory.value;
  else if (instr_info.type & TYPE_ADDR)
    addr = instr_info.addr;
  else if (instr_info.type & TYPE_VALUE)
    addr = instr_info.value.value;
  else
    return;
  if (!DbgMemIsValidReadPtr(addr)) return;
  LineEditDialog mLineEdit(this);
  mLineEdit.setTextMaxLength(MAX_LABEL_SIZE - 2);
  QString addr_text = ToPtrString(addr);
  char label_text[MAX_LABEL_SIZE] = "";
  if (DbgGetLabelAt(addr, SEG_DEFAULT, label_text))
    mLineEdit.setText(QString(label_text));
  mLineEdit.setWindowTitle(tr("Add label at ") + addr_text);
restart:
  if (mLineEdit.exec() != QDialog::Accepted) return;
  QByteArray utf8data = mLineEdit.editText.toUtf8();
  if (!utf8data.isEmpty() && DbgIsValidExpression(utf8data.constData()) &&
      DbgValFromString(utf8data.constData()) != addr) {
    QMessageBox msg(QMessageBox::Warning, tr("The label may be in use"),
                    tr("The label \"%1\" may be an existing label or a valid "
                       "expression. Using such label might have undesired "
                       "effects. Do you still want to continue?")
                        .arg(mLineEdit.editText),
                    QMessageBox::Yes | QMessageBox::No, this);
    msg.setWindowIcon(DIcon("compile-warning.png"));
    msg.setParent(this, Qt::Dialog);
    msg.setWindowFlags(msg.windowFlags() & (~Qt::WindowContextHelpButtonHint));
    if (msg.exec() == QMessageBox::No) goto restart;
  }
  if (!DbgSetLabelAt(addr, utf8data.constData()))
    SimpleErrorBox(this, tr("Error!"), tr("DbgSetLabelAt failed!"));

  GuiUpdateAllViews();
}

void CPUDisassembly::setCommentSlot() {
  if (!DbgIsDebugging()) return;
  duint wVA = rvaToVa(getInitialSelection());
  LineEditDialog mLineEdit(this);
  mLineEdit.setTextMaxLength(MAX_COMMENT_SIZE - 2);
  QString addr_text = ToPtrString(wVA);
  char comment_text[MAX_COMMENT_SIZE] = "";
  if (DbgGetCommentAt(nullptr, (duint)wVA, comment_text)) {
    if (comment_text[0] == '\1')  // automatic comment
      mLineEdit.setText(QString(comment_text + 1));
    else
      mLineEdit.setText(QString(comment_text));
  }
  mLineEdit.setWindowTitle(tr("Add comment at ") + addr_text);
  if (mLineEdit.exec() != QDialog::Accepted) return;
  QString comment = mLineEdit.editText.replace('\r', "").replace('\n', "");
  if (!DbgSetCommentAt(nullptr, wVA, comment.toUtf8().constData()))
    SimpleErrorBox(this, tr("Error!"), tr("DbgSetCommentAt failed!"));

  static bool easter = isEaster();
  if (easter && comment.toLower() == "oep") {
    QFile file(":/icons/images/egg.wav");
    if (file.open(QIODevice::ReadOnly)) {
      QByteArray egg = file.readAll();
      PlaySoundA(egg.data(), 0, SND_MEMORY | SND_ASYNC | SND_NODEFAULT);
    }
  }

  GuiUpdateAllViews();
}

void CPUDisassembly::setBookmarkSlot() {
  if (!DbgIsDebugging()) return;
  duint wVA = rvaToVa(getInitialSelection());
  bool result;
  if (DbgGetBookmarkAt(nullptr, wVA))
    result = DbgSetBookmarkAt(nullptr, wVA, false);
  else
    result = DbgSetBookmarkAt(nullptr, wVA, true);
  if (!result) {
    QMessageBox msg(QMessageBox::Critical, tr("Error!"),
                    tr("DbgSetBookmarkAt failed!"));
    msg.setWindowIcon(DIcon("compile-error.png"));
    msg.setParent(this, Qt::Dialog);
    msg.setWindowFlags(msg.windowFlags() & (~Qt::WindowContextHelpButtonHint));
    msg.exec();
  }

  GuiUpdateAllViews();
}

void CPUDisassembly::toggleFunctionSlot() {
  if (!DbgIsDebugging()) return;
  duint start = rvaToVa(getSelectionStart());
  duint end = rvaToVa(getSelectionEnd());
  duint function_start = 0;
  duint function_end = 0;
  if (!DbgFunctionOverlaps(start, end)) {
    QString cmd =
        QString("functionadd ") + ToPtrString(start) + "," + ToPtrString(end);
    DbgCmdExec(cmd);
  } else {
    for (duint i = start; i <= end; i++) {
      if (DbgFunctionGet(i, &function_start, &function_end)) break;
    }
    QString cmd = QString("functiondel ") + ToPtrString(function_start);
    DbgCmdExec(cmd);
  }
}

void CPUDisassembly::toggleArgumentSlot() {
  if (!DbgIsDebugging()) return;
  duint start = rvaToVa(getSelectionStart());
  duint end = rvaToVa(getSelectionEnd());
  duint argument_start = 0;
  duint argument_end = 0;
  if (!DbgArgumentOverlaps(start, end)) {
    QString start_text = ToPtrString(start);
    QString end_text = ToPtrString(end);

    QString cmd = "argumentadd " + start_text + "," + end_text;
    DbgCmdExec(cmd);
  } else {
    for (duint i = start; i <= end; i++) {
      if (DbgArgumentGet(i, &argument_start, &argument_end)) break;
    }
    QString start_text = ToPtrString(argument_start);

    QString cmd = "argumentdel " + start_text;
    DbgCmdExec(cmd);
  }
}

void CPUDisassembly::addLoopSlot() {
  if (!DbgIsDebugging()) return;
  duint start = rvaToVa(getSelectionStart());
  duint end = rvaToVa(getSelectionEnd());
  if (start == end) return;
  auto depth = findDeepestLoopDepth(start);
  DbgCmdExec(QString("loopadd %1, %2")
                 .arg(ToPtrString(start))
                 .arg(ToPtrString(end))
                 .arg(depth));
}

void CPUDisassembly::deleteLoopSlot() {
  if (!DbgIsDebugging()) return;
  duint start = rvaToVa(getSelectionStart());
  auto depth = findDeepestLoopDepth(start);
  if (depth < 0) return;
  DbgCmdExec(QString("loopdel %1, .%2").arg(ToPtrString(start)).arg(depth));
}

void CPUDisassembly::decompileSlot() {
  if (!DbgIsDebugging()) return;

  dsint wRVA = getInitialSelection();
  DbgCmdExec(QString("decompile %1").arg(ToPtrString(rvaToVa(wRVA))));
}

void CPUDisassembly::decompileModuleSlot() {
  if (!DbgIsDebugging()) return;

  DbgCmdExec(QString("decompile 0"));
}

void CPUDisassembly::assembleSlot() {
  if (!DbgIsDebugging()) return;

  AssembleDialog assembleDialog(this);

  do {
    dsint wRVA = getInitialSelection();
    duint wVA = rvaToVa(wRVA);
    unfold(wRVA);
    QString addr_text = ToPtrString(wVA);

    Instruction_t instr = this->DisassembleAt(wRVA);

    QString actual_inst = instr.instStr;

    bool assembly_error;
    do {
      char error[MAX_ERROR_SIZE] = "";

      assembly_error = false;

      assembleDialog.setSelectedInstrVa(wVA);
      if (ConfigBool("Disassembler", "Uppercase"))
        actual_inst = actual_inst.toUpper().replace(
            QRegularExpression("0X([0-9A-F]+)"), "0x\\1");
      assembleDialog.setTextEditValue(actual_inst);
      assembleDialog.setWindowTitle(tr("Assemble at %1").arg(addr_text));
      assembleDialog.setFillWithNopsChecked(
          ConfigBool("Disassembler", "FillNOPs"));
      assembleDialog.setKeepSizeChecked(ConfigBool("Disassembler", "KeepSize"));

      auto exec = assembleDialog.exec();

      Config()->setBool("Disassembler", "FillNOPs",
                        assembleDialog.bFillWithNopsChecked);
      Config()->setBool("Disassembler", "KeepSize",
                        assembleDialog.bKeepSizeChecked);

      if (exec != QDialog::Accepted) return;

      // sanitize the expression (just simplifying it by removing excess
      // whitespaces)
      auto expression = assembleDialog.editText.simplified();

      // if the instruction its unknown or is the old instruction or empty (easy
      // way to skip from GUI) skipping
      if (expression == QString("???") ||
          expression.toLower() == instr.instStr.toLower() ||
          expression == QString(""))
        break;

      uchar asmbin[20] = {0}, asmnop[20];
      auto* diser = DbgGlobal::inst()->diser(nullptr);
      std::string err =
          diser->assemble(expression.toUtf8().constData(), asmbin);
      if (!asmbin[0]) {
        if (assembleDialog.bFillWithNopsChecked) {
          diser->assemble("nop", asmnop);
          memcpy(asmbin, asmnop, asmnop[0]);
        }
      } else {
        err.clear();
      }
      if (asmbin[0]) {
        mMemPage->write(&asmbin[1], wRVA, asmbin[0]);
      }
      if (err.size()) {
        QMessageBox msg(QMessageBox::Critical, tr("Error!"),
                        tr("Failed to assemble instruction \" %1 \" (%2)")
                            .arg(expression)
                            .arg(error));
        msg.setWindowIcon(DIcon("compile-error.png"));
        msg.setParent(this, Qt::Dialog);
        msg.setWindowFlags(msg.windowFlags() &
                           (~Qt::WindowContextHelpButtonHint));
        msg.exec();
        actual_inst = expression;
        assembly_error = true;
      }
    } while (assembly_error);

    // select next instruction after assembling
    setSingleSelection(wRVA);

    dsint botRVA = getTableOffset();
    dsint topRVA =
        getInstructionRVA(getTableOffset(), getNbrOfLineToPrint() - 1);

    dsint wInstrSize = getInstructionRVA(wRVA, 1) - wRVA - 1;

    expandSelectionUpTo(wRVA + wInstrSize);
    selectNext(false);

    if (getSelectionStart() < botRVA)
      setTableOffset(getSelectionStart());
    else if (getSelectionEnd() >= topRVA)
      setTableOffset(
          getInstructionRVA(getSelectionEnd(), -getNbrOfLineToPrint() + 2));

    // refresh view
    GuiUpdateAllViews();
  } while (1);
}

void CPUDisassembly::gotoExpressionSlot() {
  if (!DbgIsDebugging()) return;
  if (!mGoto) mGoto = new GotoDialog(this);
  mGoto->setInitialExpression(ToPtrString(rvaToVa(getInitialSelection())));
  if (mGoto->exec() == QDialog::Accepted) {
    duint value = DbgValFromString(mGoto->expressionText.toUtf8().constData());
    gotoAddress(value);
  }
}

void CPUDisassembly::gotoFileOffsetSlot() {
  if (!DbgIsDebugging()) return;
  char modname[MAX_MODULE_SIZE] = "";
  if (!DbgFunctions()->ModNameFromAddr(rvaToVa(getInitialSelection()), modname,
                                       true)) {
    QMessageBox::critical(this, tr("Error!"), tr("Not inside a module..."));
    return;
  }
  if (!mGotoOffset) mGotoOffset = new GotoDialog(this);
  mGotoOffset->fileOffset = true;
  mGotoOffset->modName = QString(modname);
  mGotoOffset->setWindowTitle(tr("Goto File Offset in ") + QString(modname));
  QString offsetOfSelected;
  prepareDataRange(getSelectionStart(), getSelectionEnd(),
                   [&](int, const Instruction_t& inst) {
                     duint addr = rvaToVa(inst.rva);
                     duint offset = DbgFunctions()->VaToFileOffset(addr);
                     if (offset) offsetOfSelected = ToHexString(offset);
                     return false;
                   });
  if (!offsetOfSelected.isEmpty())
    mGotoOffset->setInitialExpression(offsetOfSelected);
  if (mGotoOffset->exec() != QDialog::Accepted) return;
  duint value =
      DbgValFromString(mGotoOffset->expressionText.toUtf8().constData());
  value = DbgFunctions()->FileOffsetToVa(modname, value);
  gotoAddress(value);
}

void CPUDisassembly::gotoStartSlot() {
  duint dest = mMemPage->getBase();
  gotoAddress(dest);
}

void CPUDisassembly::gotoEndSlot() {
  duint dest =
      mMemPage->getBase() + mMemPage->getSize() - (getViewableRowsCount() * 16);
  gotoAddress(dest);
}

void CPUDisassembly::gotoFunctionStartSlot() {
  duint start;
  if (!DbgFunctionGet(rvaToVa(getInitialSelection()), &start, nullptr)) return;
  gotoAddress(start);
}

void CPUDisassembly::gotoFunctionEndSlot() {
  duint end;
  if (!DbgFunctionGet(rvaToVa(getInitialSelection()), nullptr, &end)) return;
  gotoAddress(end);
}

void CPUDisassembly::gotoPreviousReferenceSlot() {
  auto count = DbgEval("refsearch.count()"),
       index = DbgEval("$__disasm_refindex"),
       addr = DbgEval("refsearch.addr($__disasm_refindex)");
  if (count) {
    if (index > 0 && addr == rvaToVa(getInitialSelection()))
      DbgValToString("$__disasm_refindex", index - 1);
    gotoAddress(DbgValFromString("refsearch.addr($__disasm_refindex)"));
  }
}

void CPUDisassembly::gotoNextReferenceSlot() {
  auto count = DbgEval("refsearch.count()"),
       index = DbgEval("$__disasm_refindex"),
       addr = DbgEval("refsearch.addr($__disasm_refindex)");
  if (count) {
    if (index + 1 < count && addr == rvaToVa(getInitialSelection()))
      DbgValToString("$__disasm_refindex", index + 1);
    gotoAddress(DbgValFromString("refsearch.addr($__disasm_refindex)"));
  }
}

void CPUDisassembly::gotoXrefSlot() {
  if (!DbgIsDebugging() || !mXrefInfo.refcount) return;
  if (!mXrefDlg) mXrefDlg = new XrefBrowseDialog(this);
  mXrefDlg->setup(getSelectedVa(), [this](duint addr) { gotoAddress(addr); });
  mXrefDlg->showNormal();
}

void CPUDisassembly::followActionSlot() {
  QAction* action = qobject_cast<QAction*>(sender());
  if (!action) return;
  if (action->objectName().startsWith("DUMP|"))
    DbgCmdExec(QString().sprintf(
        "dump %s", action->objectName().mid(5).toUtf8().constData()));
  else if (action->objectName().startsWith("REF|")) {
    QString addrText = ToPtrString(rvaToVa(getInitialSelection()));
    QString value = action->objectName().mid(4);
    DbgCmdExec(QString("findref \"" + value + "\", " + addrText));
    emit displayReferencesWidget();
  } else if (action->objectName().startsWith("CPU|")) {
    QString value = action->objectName().mid(4);
    gotoAddress(DbgValFromString(value.toUtf8().constData()));
  }
}

void CPUDisassembly::gotoPreviousSlot() { historyPrevious(); }

void CPUDisassembly::gotoNextSlot() { historyNext(); }

void CPUDisassembly::findReferencesSlot() {
  QString addrStart = ToPtrString(rvaToVa(getSelectionStart()));
  QString addrEnd = ToPtrString(rvaToVa(getSelectionEnd()));
  QString addrDisasm = ToPtrString(rvaToVa(getInitialSelection()));
  DbgCmdExec(QString("findrefrange " + addrStart + ", " + addrEnd + ", " +
                     addrDisasm));
  emit displayReferencesWidget();
}

void CPUDisassembly::findConstantSlot() {
  int refFindType = 0;
  if (sender() == mFindConstantRegion)
    refFindType = 0;
  else if (sender() == mFindConstantModule)
    refFindType = 1;
  else if (sender() == mFindConstantAll)
    refFindType = 2;
  else if (sender() == mFindConstantFunction)
    refFindType = -1;

  WordEditDialog wordEdit(this);
  wordEdit.setup(tr("Enter Constant"), 0, sizeof(dsint));
  if (wordEdit.exec() != QDialog::Accepted)  // cancel pressed
    return;

  auto addrText = ToHexString(rvaToVa(getInitialSelection()));
  auto constText = ToHexString(wordEdit.getVal());
  if (refFindType != -1)
    DbgCmdExec(QString("findref %1, %2, 0, %3")
                   .arg(constText)
                   .arg(addrText)
                   .arg(refFindType));
  else {
    duint start, end;
    if (DbgFunctionGet(rvaToVa(getInitialSelection()), &start, &end))
      DbgCmdExec(QString("findref %1, %2, %3, 0")
                     .arg(constText)
                     .arg(ToPtrString(start))
                     .arg(ToPtrString(end - start)));
  }
  emit displayReferencesWidget();
}

void CPUDisassembly::findStringsSlot() {
  int refFindType = 0;
  if (sender() == mFindStringsRegion)
    refFindType = 0;
  else if (sender() == mFindStringsModule)
    refFindType = 1;
  else if (sender() == mFindStringsAll)
    refFindType = 2;
  else if (sender() == mFindStringsFunction) {
    duint start, end;
    if (DbgFunctionGet(rvaToVa(getInitialSelection()), &start, &end))
      DbgCmdExec(QString("strref %1, %2, 0")
                     .arg(ToPtrString(start))
                     .arg(ToPtrString(end - start)));
    return;
  }

  auto addrText = ToHexString(rvaToVa(getInitialSelection()));
  DbgCmdExec(QString("strref %1, 0, %2").arg(addrText).arg(refFindType));
  emit displayReferencesWidget();
}

void CPUDisassembly::findCallsSlot() {
  int refFindType = 0;
  if (sender() == mFindCallsRegion)
    refFindType = 0;
  else if (sender() == mFindCallsModule)
    refFindType = 1;
  else if (sender() == mFindCallsAll)
    refFindType = 2;
  else if (sender() == mFindCallsFunction)
    refFindType = -1;

  auto addrText = ToHexString(rvaToVa(getInitialSelection()));
  if (refFindType != -1)
    DbgCmdExec(QString("modcallfind %1, .0, %2").arg(addrText).arg(refFindType));
  else {
    duint start, end;
    if (DbgFunctionGet(rvaToVa(getInitialSelection()), &start, &end))
      DbgCmdExec(QString("modcallfind %1, .%2, 0")
                     .arg(ToPtrString(start))
                     .arg(end - start));
  }
  emit displayReferencesWidget();
}

void CPUDisassembly::findPatternSlot() {
  HexEditDialog hexEdit(this);
  hexEdit.showEntireBlock(true);
  hexEdit.isDataCopiable(false);
  hexEdit.mHexEdit->setOverwriteMode(false);
  hexEdit.setWindowTitle(tr("Find Pattern..."));
  if (hexEdit.exec() != QDialog::Accepted) return;

  dsint addr = rvaToVa(getSelectionStart());
  if (hexEdit.entireBlock()) addr = DbgMemFindBaseAddr(addr, 0);

  QString command;
  if (sender() == mFindPatternModule) {
    auto base = DbgFunctions()->ModBaseFromAddr(addr);
    if (base)
      command = QString("findallmem %1, %2, .%3")
                    .arg(ToHexString(base)).arg(hexEdit.mHexEdit->pattern())
                    .arg((int)DbgFunctions()->ModSizeFromAddr(base));
  }
  if (sender() == mFindPatternFunction) {
    duint start, end;
    if (DbgFunctionGet(addr, &start, &end))
      command = QString("findall %1, %2, .%3")
                    .arg(ToPtrString(start))
                    .arg(hexEdit.mHexEdit->pattern())
                    .arg(end - start);
    else
      return;
  }
  if (sender() == mFindPatternAll)
    command = QString("findallmem  %1, %2, %3")
                  .arg(ToPtrString(addr))
                  .arg(hexEdit.mHexEdit->pattern())
                  .arg("&data&");
  if (!command.length())
    command = QString("findall %1, %2")
                  .arg(ToHexString(addr), hexEdit.mHexEdit->pattern());

  DbgCmdExec(command);
  emit displayReferencesWidget();
}

void CPUDisassembly::findGUIDSlot() {
  int refFindType = 0;
  if (sender() == mFindGUIDRegion)
    refFindType = 0;
  else if (sender() == mFindGUIDModule)
    refFindType = 1;
  else if (sender() == mFindGUIDAll)
    refFindType = 2;
  else if (sender() == mFindGUIDFunction)
    refFindType = -1;

  auto addrText = ToHexString(rvaToVa(getInitialSelection()));
  if (refFindType == -1)
    DbgCmdExec(QString("findguid %1, 0, %2").arg(addrText).arg(refFindType));
  else {
    duint start, end;
    if (DbgFunctionGet(rvaToVa(getInitialSelection()), &start, &end))
      DbgCmdExec(QString("findguid %1, %2, 0")
                     .arg(ToPtrString(start))
                     .arg(ToPtrString(end - start)));
  }
  emit displayReferencesWidget();
}

void CPUDisassembly::findNamesSlot() {
  if (sender() == mFindNamesModule) {
    auto base = DbgFunctions()->ModBaseFromAddr(rvaToVa(getInitialSelection()));
    if (!base) return;
    Bridge::getBridge()->symbolSelectModule(base);
    emit displaySymbolsWidget();
  }
}

void CPUDisassembly::selectionGetSlot(SELECTIONDATA* selection) {
  selection->start = rvaToVa(getSelectionStart());
  selection->end = rvaToVa(getSelectionEnd());
  Bridge::getBridge()->setResult(BridgeResult::SelectionGet, 1);
}

void CPUDisassembly::selectionSetSlot(const SELECTIONDATA* selection) {
  dsint selMin = getBase();
  dsint selMax = selMin + getSize();
  dsint start = selection->start;
  dsint end = selection->end;
  if (start < selMin || start >= selMax || end < selMin ||
      end >= selMax)  // selection out of range
  {
    Bridge::getBridge()->setResult(BridgeResult::SelectionSet, 0);
    return;
  }
  setSingleSelection(start - selMin);
  expandSelectionUpTo(end - selMin);
  reloadData();
  Bridge::getBridge()->setResult(BridgeResult::SelectionSet, 1);
}

void CPUDisassembly::selectionUpdatedSlot() {
  QString selStart = ToPtrString(rvaToVa(getSelectionStart()));
  QString selEnd = ToPtrString(rvaToVa(getSelectionEnd()));
  QString info = tr("Disassembly");
  char mod[MAX_MODULE_SIZE] = "";
  if (DbgFunctions()->ModNameFromAddr(rvaToVa(getSelectionStart()), mod, true))
    info = QString(mod) + "";
  GuiAddStatusBarMessage(
      QString(info + ": " + selStart + " -> " + selEnd +
              QString().sprintf(" (0x%.8X bytes)\n",
                                getSelectionEnd() - getSelectionStart() + 1))
          .toUtf8()
          .constData());
}

void CPUDisassembly::enableHighlightingModeSlot() {
  if (mHighlightingMode)
    mHighlightingMode = false;
  else
    mHighlightingMode = true;
  reloadData();
}

void CPUDisassembly::binaryEditSlot() {
  HexEditDialog hexEdit(this);
  dsint selStart = getSelectionStart();
  dsint selSize = getSelectionEnd() - selStart + 1;
  byte_t* data = new byte_t[selSize];
  mMemPage->read(data, selStart, selSize);
  hexEdit.mHexEdit->setData(QByteArray((const char*)data, selSize));
  delete[] data;
  hexEdit.setWindowTitle(
      tr("Edit code at %1").arg(ToPtrString(rvaToVa(selStart))));
  if (hexEdit.exec() != QDialog::Accepted) return;
  dsint dataSize = hexEdit.mHexEdit->data().size();
  dsint newSize = selSize > dataSize ? selSize : dataSize;
  data = new byte_t[newSize];
  mMemPage->read(data, selStart, newSize);
  QByteArray patched =
      hexEdit.mHexEdit->applyMaskedData(QByteArray((const char*)data, newSize));
  mMemPage->write(patched.constData(), selStart, patched.size());
  GuiUpdateAllViews();
}

void CPUDisassembly::binaryFillSlot() {
  HexEditDialog hexEdit(this);
  hexEdit.showKeepSize(false);
  hexEdit.mHexEdit->setOverwriteMode(false);
  dsint selStart = getSelectionStart();
  hexEdit.setWindowTitle(
      tr("Fill code at %1").arg(ToPtrString(rvaToVa(selStart))));
  if (hexEdit.exec() != QDialog::Accepted) return;
  QString pattern = hexEdit.mHexEdit->pattern();
  dsint selSize = getSelectionEnd() - selStart + 1;
  byte_t* data = new byte_t[selSize];
  mMemPage->read(data, selStart, selSize);
  hexEdit.mHexEdit->setData(QByteArray((const char*)data, selSize));
  delete[] data;
  hexEdit.mHexEdit->fill(0, QString(pattern));
  QByteArray patched(hexEdit.mHexEdit->data());
  mMemPage->write(patched, selStart, patched.size());
  GuiUpdateAllViews();
}

void CPUDisassembly::binaryFillNopsSlot() {
  HexEditDialog hexEdit(this);
  dsint selStart = getSelectionStart();
  dsint selSize = getSelectionEnd() - selStart + 1;
  WordEditDialog mLineEdit(this);
  mLineEdit.setup(tr("Size"), selSize, sizeof(duint));
  if (mLineEdit.exec() != QDialog::Accepted || !mLineEdit.getVal()) return;
  selSize = mLineEdit.getVal();

#if 0
    byte_t* data = new byte_t[selSize];
    mMemPage->read(data, selStart, selSize);
    hexEdit.mHexEdit->setData(QByteArray((const char*)data, selSize));
    delete [] data;
    hexEdit.mHexEdit->fill(0, QString("90"));
    QByteArray patched(hexEdit.mHexEdit->data());
    mMemPage->write(patched, selStart, patched.size());
#else
  uchar nop[20];
  DbgGlobal::inst()->diser(nullptr)->assemble("nop", nop);
  for (dsint i = 0; i < selSize / nop[0]; i++) {
    mMemPage->write(&nop[1], selStart + nop[0] * i, nop[0]);
  }
#endif

  GuiUpdateAllViews();
}

void CPUDisassembly::binaryCopySlot() {
  HexEditDialog hexEdit(this);
  dsint selStart = getSelectionStart();
  dsint selSize = getSelectionEnd() - selStart + 1;
  byte_t* data = new byte_t[selSize];
  mMemPage->read(data, selStart, selSize);
  hexEdit.mHexEdit->setData(QByteArray((const char*)data, selSize));
  delete[] data;
  Bridge::CopyToClipboard(hexEdit.mHexEdit->pattern(true));
}

void CPUDisassembly::binaryPasteSlot() {
  if (!QApplication::clipboard()->mimeData()->hasText()) return;
  HexEditDialog hexEdit(this);
  dsint selStart = getSelectionStart();
  dsint selSize = getSelectionEnd() - selStart + 1;
  QClipboard* clipboard = QApplication::clipboard();
  hexEdit.mHexEdit->setData(clipboard->text());

  byte_t* data = new byte_t[selSize];
  mMemPage->read(data, selStart, selSize);
  QByteArray patched =
      hexEdit.mHexEdit->applyMaskedData(QByteArray((const char*)data, selSize));
  if (patched.size() < selSize) selSize = patched.size();
  mMemPage->write(patched.constData(), selStart, selSize);
  GuiUpdateAllViews();
}

void CPUDisassembly::undoSelectionSlot() {
  dsint start = rvaToVa(getSelectionStart());
  dsint end = rvaToVa(getSelectionEnd());
  if (!DbgFunctions()->PatchInRange(start,
                                    end))  // nothing patched in selected range
    return;
  DbgFunctions()->PatchRestoreRange(start, end);
  reloadData();
}

void CPUDisassembly::binaryPasteIgnoreSizeSlot() {
  if (!QApplication::clipboard()->mimeData()->hasText()) return;
  HexEditDialog hexEdit(this);
  dsint selStart = getSelectionStart();
  dsint selSize = getSelectionEnd() - selStart + 1;
  QClipboard* clipboard = QApplication::clipboard();
  hexEdit.mHexEdit->setData(clipboard->text());

  byte_t* data = new byte_t[selSize];
  mMemPage->read(data, selStart, selSize);
  QByteArray patched =
      hexEdit.mHexEdit->applyMaskedData(QByteArray((const char*)data, selSize));
  delete[] data;
  mMemPage->write(patched.constData(), selStart, patched.size());
  GuiUpdateAllViews();
}

void CPUDisassembly::showPatchesSlot() { emit showPatches(); }

void CPUDisassembly::copySelectionSlot(bool copyBytes) {
  QString selectionString = "";
  QString selectionHtmlString = "";
  QTextStream stream(&selectionString);
  QTextStream htmlStream(&selectionHtmlString);
  pushSelectionInto(copyBytes, stream, &htmlStream);
  Bridge::CopyToClipboard(selectionString, selectionHtmlString);
}

void CPUDisassembly::copySelectionToFileSlot(bool copyBytes) {
  QString fileName = QFileDialog::getSaveFileName(this, tr("Open File"), "",
                                                  tr("Text Files (*.txt)"));
  if (fileName != "") {
    QFile file(fileName);
    if (!file.open(QIODevice::WriteOnly)) {
      QMessageBox::critical(this, tr("Error"), tr("Could not open file"));
      return;
    }

    QTextStream stream(&file);
    pushSelectionInto(copyBytes, stream);
    file.close();
  }
}

void CPUDisassembly::setSideBar(CPUSideBar* sideBar) { mSideBar = sideBar; }

void CPUDisassembly::pushSelectionInto(bool copyBytes, QTextStream& stream,
                                       QTextStream* htmlStream) {
  const int addressLen = getColumnWidth(0) / getCharWidth() - 1;
  const int bytesLen = getColumnWidth(1) / getCharWidth() - 1;
  const int disassemblyLen = getColumnWidth(2) / getCharWidth() - 1;
  if (htmlStream)
    *htmlStream << QString(
                       "<table "
                       "style=\"border-width:0px;border-color:#000000;font-"
                       "family:%1;font-size:%2px;\">")
                       .arg(font().family().toHtmlEscaped())
                       .arg(getRowHeight());
  prepareDataRange(
      getSelectionStart(), getSelectionEnd(),
      [&](int i, const Instruction_t& inst) {
        if (i) stream << "\r\n";
        duint cur_addr = rvaToVa(inst.rva);
        QString address =
            getAddrText(cur_addr, 0, addressLen > sizeof(duint) * 2 + 1);
        QString bytes;
        QString bytesHtml;
        if (copyBytes)
          RichTextPainter::htmlRichText(getRichBytes(inst, false), bytesHtml,
                                        bytes);
        QString disassembly;
        QString htmlDisassembly;
        if (htmlStream) {
          RichTextPainter::List richText;
          if (mHighlightToken.text.length())
            ZydisTokenizer::TokenToRichText(inst.tokens, richText,
                                            &mHighlightToken);
          else
            ZydisTokenizer::TokenToRichText(inst.tokens, richText, 0);
          RichTextPainter::htmlRichText(richText, htmlDisassembly, disassembly);
        } else {
          for (const auto& token : inst.tokens.tokens)
            disassembly += token.text;
        }
        QString fullComment;
        QString comment;
        bool autocomment;
        if (GetCommentFormat(cur_addr, comment, &autocomment))
          fullComment = " " + comment;
        stream << address.leftJustified(addressLen, QChar(' '), true);
        if (copyBytes)
          stream << " | " + bytes.leftJustified(bytesLen, QChar(' '), true);
        stream << " | " +
                      disassembly.leftJustified(disassemblyLen, QChar(' '),
                                                true) +
                      " |" + fullComment;
        if (htmlStream) {
          *htmlStream << QString("<tr><td>%1</td><td>%2</td><td>%3</td><td>")
                             .arg(address.toHtmlEscaped(), bytesHtml,
                                  htmlDisassembly);
          if (!comment.isEmpty()) {
            if (autocomment) {
              *htmlStream << QString("<span style=\"color:%1")
                                 .arg(mAutoCommentColor.name());
              if (mAutoCommentBackgroundColor != Qt::transparent)
                *htmlStream << QString(";background-color:%2")
                                   .arg(mAutoCommentBackgroundColor.name());
            } else {
              *htmlStream << QString("<span style=\"color:%1")
                                 .arg(mCommentColor.name());
              if (mCommentBackgroundColor != Qt::transparent)
                *htmlStream << QString(";background-color:%2")
                                   .arg(mCommentBackgroundColor.name());
            }
            *htmlStream << "\">" << comment.toHtmlEscaped()
                        << "</span></td></tr>";
          } else {
            char label[MAX_LABEL_SIZE];
            if (DbgGetLabelAt(cur_addr, SEG_DEFAULT, label)) {
              comment = QString::fromUtf8(label);
              *htmlStream
                  << QString("<span style=\"color:%1").arg(mLabelColor.name());
              if (mLabelBackgroundColor != Qt::transparent)
                *htmlStream << QString(";background-color:%2")
                                   .arg(mLabelBackgroundColor.name());
              *htmlStream << "\">" << comment.toHtmlEscaped()
                          << "</span></td></tr>";
            } else
              *htmlStream << "</td></tr>";
          }
        }
        return true;
      });
  if (htmlStream) *htmlStream << "</table>";
}

void CPUDisassembly::copySelectionSlot() { copySelectionSlot(true); }

void CPUDisassembly::copySelectionToFileSlot() {
  copySelectionToFileSlot(true);
}

void CPUDisassembly::copySelectionNoBytesSlot() { copySelectionSlot(false); }

void CPUDisassembly::copySelectionToFileNoBytesSlot() {
  copySelectionToFileSlot(false);
}

void CPUDisassembly::copyAddressSlot() {
  QString clipboard = "";
  prepareDataRange(getSelectionStart(), getSelectionEnd(),
                   [&](int i, const Instruction_t& inst) {
                     if (i) clipboard += "\r\n";
                     clipboard += ToPtrString(rvaToVa(inst.rva));
                     return true;
                   });
  Bridge::CopyToClipboard(clipboard);
}

void CPUDisassembly::copyFileAddressSlot() {
  QString clipboard = "";
  prepareDataRange(getSelectionStart(), getSelectionEnd(),
                   [&](int i, const Instruction_t& inst) {
                     if (i) clipboard += "\r\n";
                     clipboard += ToPtrString(
                         DbgGlobal::inst()->debugee->db->baseaddr + inst.rva);
                     return true;
                   });
  Bridge::CopyToClipboard(clipboard);
}

void CPUDisassembly::copyRvaSlot() {
  QString clipboard = "";
  prepareDataRange(getSelectionStart(), getSelectionEnd(),
                   [&](int i, const Instruction_t& inst) {
                     if (i) clipboard += "\r\n";
                     duint addr = rvaToVa(inst.rva);
                     duint base = DbgFunctions()->ModBaseFromAddr(addr);
                     if (base)
                       clipboard += ToHexString(addr - base);
                     else {
                       SimpleWarningBox(this, tr("Error!"),
                                        tr("Selection not in a module..."));
                       return false;
                     }
                     return true;
                   });
  Bridge::CopyToClipboard(clipboard);
}

void CPUDisassembly::copyFileOffsetSlot() {
  QString clipboard = "";
  prepareDataRange(getSelectionStart(), getSelectionEnd(),
                   [&](int i, const Instruction_t& inst) {
                     if (i) clipboard += "\r\n";
                     duint addr = rvaToVa(inst.rva);
                     duint offset = DbgFunctions()->VaToFileOffset(addr);
                     if (offset)
                       clipboard += ToHexString(offset);
                     else {
                       SimpleErrorBox(this, tr("Error!"),
                                      tr("Selection not in a file..."));
                       return false;
                     }
                     return true;
                   });
  Bridge::CopyToClipboard(clipboard);
}

void CPUDisassembly::copyHeaderVaSlot() {
  QString clipboard = "";
  prepareDataRange(
      getSelectionStart(), getSelectionEnd(),
      [&](int i, const Instruction_t& inst) {
        if (i) clipboard += "\r\n";
        duint addr = rvaToVa(inst.rva);
        duint base = DbgFunctions()->ModBaseFromAddr(addr);
        if (base) {
          auto expr = QString("mod.headerva(0x%1)").arg(ToPtrString(addr));
          clipboard += ToPtrString(DbgValFromString(expr.toUtf8().constData()));
        } else {
          SimpleWarningBox(this, tr("Error!"),
                           tr("Selection not in a module..."));
          return false;
        }
        return true;
      });
  Bridge::CopyToClipboard(clipboard);
}

void CPUDisassembly::copyDisassemblySlot() {
  QString clipboardHtml =
      QString("<div style=\"font-family: %1; font-size: %2px\">")
          .arg(font().family())
          .arg(getRowHeight());
  QString clipboard = "";
  prepareDataRange(
      getSelectionStart(), getSelectionEnd(),
      [&](int i, const Instruction_t& inst) {
        if (i) {
          clipboard += "\r\n";
          clipboardHtml += "<br/>";
        }
        RichTextPainter::List richText;
        if (mHighlightToken.text.length())
          ZydisTokenizer::TokenToRichText(inst.tokens, richText,
                                          &mHighlightToken);
        else
          ZydisTokenizer::TokenToRichText(inst.tokens, richText, 0);
        RichTextPainter::htmlRichText(richText, clipboardHtml, clipboard);
        return true;
      });
  clipboardHtml += QString("</div>");
  Bridge::CopyToClipboard(clipboard, clipboardHtml);
}

void CPUDisassembly::labelCopySlot() {
  QString symbol = ((QAction*)sender())->text();
  Bridge::CopyToClipboard(symbol);
}

void CPUDisassembly::findCommandSlot() {
  if (!DbgIsDebugging()) return;

  int refFindType = 0;
  if (sender() == mFindCommandRegion)
    refFindType = 0;
  else if (sender() == mFindCommandModule)
    refFindType = 1;
  else if (sender() == mFindCommandFunction)
    refFindType = -1;
  else if (sender() == mFindCommandAll)
    refFindType = 2;

  LineEditDialog mLineEdit(this);
  mLineEdit.enableCheckBox(refFindType == 0);
  mLineEdit.setCheckBoxText(tr("Entire &Block"));
  mLineEdit.setCheckBox(ConfigBool("Disassembler", "FindCommandEntireBlock"));
  mLineEdit.setWindowTitle("Find Command");
  if (mLineEdit.exec() != QDialog::Accepted) return;
  Config()->setBool("Disassembler", "FindCommandEntireBlock",
                    mLineEdit.bChecked);

  char error[MAX_ERROR_SIZE] = "";
  unsigned char dest[16];
  int asmsize = 0;
  duint va = rvaToVa(getInitialSelection());
  if (mLineEdit.bChecked)  // entire block
    va = mMemPage->getBase();

  if (!DbgFunctions()->Assemble(
          mMemPage->getBase() + mMemPage->getSize() / 2, dest, &asmsize,
          mLineEdit.editText.toUtf8().constData(), error)) {
    SimpleErrorBox(this, tr("Error!"),
                   tr("Failed to assemble instruction \"") +
                       mLineEdit.editText + "\" (" + error + ")");
    return;
  }

  QString addr_text = ToPtrString(va);

  dsint size = mMemPage->getSize();
  if (refFindType != -1)
    DbgCmdExec(QString("findasm \"%1\", %2, .%3, %4")
                   .arg(mLineEdit.editText)
                   .arg(addr_text)
                   .arg(size)
                   .arg(refFindType));
  else {
    duint start, end;
    if (DbgFunctionGet(va, &start, &end))
      DbgCmdExec(QString("findasm \"%1\", %2, .%3, 0")
                     .arg(mLineEdit.editText)
                     .arg(ToPtrString(start))
                     .arg(ToPtrString(end - start)));
  }

  emit displayReferencesWidget();
}

void CPUDisassembly::openSourceSlot() {
  char szSourceFile[MAX_STRING_SIZE] = "";
  int line = 0;
  auto sel = rvaToVa(getInitialSelection());
  if (!DbgFunctions()->GetSourceFromAddr(sel, szSourceFile, &line)) return;
  emit Bridge::getBridge()->loadSourceFile(szSourceFile, sel);
  emit displaySourceManagerWidget();
}

void CPUDisassembly::displayWarningSlot(QString title, QString text) {
  SimpleWarningBox(this, title, text);
}

void CPUDisassembly::paintEvent(QPaintEvent* event) {
  // Hook/hack to update the sidebar at the same time as this widget.
  // Ensures the two widgets are synced and prevents "draw lag"
  if (mSideBar) mSideBar->reload();

  // Signal to render the original content
  Disassembly::paintEvent(event);
}

int CPUDisassembly::findDeepestLoopDepth(duint addr) {
  for (int depth = 0;; depth++)
    if (!DbgLoopGet(depth, addr, nullptr, nullptr)) return depth - 1;
  return -1;  // unreachable
}

bool CPUDisassembly::getLabelsFromInstruction(duint addr,
                                              QSet<QString>& labels) {
  BASIC_INSTRUCTION_INFO basicinfo;
  DbgDisasmFastAt(addr, &basicinfo);
  std::vector<duint> values = {addr, basicinfo.addr, basicinfo.value.value,
                               basicinfo.memory.value};
  for (auto value : values) {
    char label_[MAX_LABEL_SIZE] = "";
    if (DbgGetLabelAt(value, SEG_DEFAULT, label_)) {
      // TODO: better cleanup of names
      QString label(label_);
      if (label.endsWith("A") || label.endsWith("W"))
        label = label.left(label.length() - 1);
      if (label.startsWith("&")) label = label.right(label.length() - 1);
      labels.insert(label);
    }
  }
  return labels.size() != 0;
}

void CPUDisassembly::labelHelpSlot() {
  QString topic = ((QAction*)sender())->text();
  char setting[MAX_SETTING_SIZE] = "";
  if (!BridgeSettingGet("Misc", "HelpOnSymbolicNameUrl", setting)) {
    //"execute://winhlp32.exe -k@topic ..\\win32.hlp";
    strcpy_s(setting, "https://www.google.com/search?q=@topic");
    BridgeSettingSet("Misc", "HelpOnSymbolicNameUrl", setting);
  }
  QString baseUrl(setting);
  QString fullUrl = baseUrl.replace("@topic", topic);

  if (fullUrl.startsWith("execute://")) {
    QString command = fullUrl.right(fullUrl.length() - 10);
    QProcess::execute(command);
  } else {
    QDesktopServices::openUrl(QUrl(fullUrl));
  }
}

void CPUDisassembly::ActionTraceRecordBitSlot() {
  if (!DbgIsDebugging()) return;
  duint base = mMemPage->getBase();
  duint size = mMemPage->getSize();
  for (duint i = base; i < base + size; i += 4096) {
    if (!(DbgFunctions()->SetTraceRecordType(
            i, TRACERECORDTYPE::TraceRecordBitExec))) {
      GuiAddLogMessage(
          tr("Failed to set trace record.\n").toUtf8().constData());
      break;
    }
  }
  DbgCmdExec("traceexecute cip");
}

void CPUDisassembly::ActionTraceRecordByteSlot() {
  if (!DbgIsDebugging()) return;
  duint base = mMemPage->getBase();
  duint size = mMemPage->getSize();
  for (duint i = base; i < base + size; i += 4096) {
    if (!(DbgFunctions()->SetTraceRecordType(
            i, TRACERECORDTYPE::TraceRecordByteWithExecTypeAndCounter))) {
      GuiAddLogMessage(
          tr("Failed to set trace record.\n").toUtf8().constData());
      break;
    }
  }
  DbgCmdExec("traceexecute cip");
}

void CPUDisassembly::ActionTraceRecordWordSlot() {
  if (!DbgIsDebugging()) return;
  duint base = mMemPage->getBase();
  duint size = mMemPage->getSize();
  for (duint i = base; i < base + size; i += 4096) {
    if (!(DbgFunctions()->SetTraceRecordType(
            i, TRACERECORDTYPE::TraceRecordWordWithExecTypeAndCounter))) {
      GuiAddLogMessage(
          tr("Failed to set trace record.\n").toUtf8().constData());
      break;
    }
  }
  DbgCmdExec("traceexecute cip");
}

void CPUDisassembly::ActionTraceRecordDisableSlot() {
  if (!DbgIsDebugging()) return;
  duint base = mMemPage->getBase();
  duint size = mMemPage->getSize();
  for (duint i = base; i < base + size; i += 4096) {
    if (!(DbgFunctions()->SetTraceRecordType(
            i, TRACERECORDTYPE::TraceRecordNone))) {
      GuiAddLogMessage(
          tr("Failed to set trace record.\n").toUtf8().constData());
      break;
    }
  }
}

void CPUDisassembly::mnemonicBriefSlot() {
  mShowMnemonicBrief = !mShowMnemonicBrief;
  reloadData();
}

void CPUDisassembly::mnemonicHelpSlot() {
  unsigned char data[16] = {0xCC};
  auto addr = rvaToVa(getInitialSelection());
  DbgMemRead(addr, data, sizeof(data));
  Zydis zydis;
  zydis.Disassemble(addr, data);
  DbgCmdExecDirect(QString("mnemonichelp %1").arg(zydis.Mnemonic().c_str()));
  emit displayLogWidget();
}

void CPUDisassembly::analyzeSingleFunctionSlot() {
  DbgCmdExec(
      QString("analr %1").arg(ToHexString(rvaToVa(getInitialSelection()))));
}

void CPUDisassembly::removeAnalysisSelectionSlot() {
  if (!DbgIsDebugging()) return;
  WordEditDialog mLineEdit(this);
  mLineEdit.setup(tr("Size"), getSelectionSize(), sizeof(duint));
  if (mLineEdit.exec() != QDialog::Accepted || !mLineEdit.getVal()) return;
  mDisasm->getEncodeMap()->delRange(rvaToVa(getSelectionStart()),
                                    mLineEdit.getVal());
  GuiUpdateDisassemblyView();
}

void CPUDisassembly::removeAnalysisModuleSlot() {
  if (!DbgIsDebugging()) return;
  mDisasm->getEncodeMap()->delSegment(rvaToVa(getSelectionStart()));
  GuiUpdateDisassemblyView();
}

void CPUDisassembly::setEncodeTypeRangeSlot() {
  if (!DbgIsDebugging()) return;
  QAction* pAction = qobject_cast<QAction*>(sender());
  WordEditDialog mLineEdit(this);
  mLineEdit.setup(tr("Size"), getSelectionSize(), sizeof(duint));
  if (mLineEdit.exec() != QDialog::Accepted || !mLineEdit.getVal()) return;
  mDisasm->getEncodeMap()->setDataType(rvaToVa(getSelectionStart()),
                                       mLineEdit.getVal(),
                                       (ENCODETYPE)pAction->data().toUInt());
  setSingleSelection(getSelectionStart());
  GuiUpdateDisassemblyView();
}

void CPUDisassembly::setEncodeTypeSlot() {
  if (!DbgIsDebugging()) return;
  QAction* pAction = qobject_cast<QAction*>(sender());
  mDisasm->getEncodeMap()->setDataType(rvaToVa(getSelectionStart()),
                                       (ENCODETYPE)pAction->data().toUInt());
  setSingleSelection(getSelectionStart());
  GuiUpdateDisassemblyView();
}

void CPUDisassembly::graphSlot() {
  if (DbgCmdExecDirect(QString("graph %1")
                           .arg(ToPtrString(rvaToVa(getSelectionStart())))
                           .toUtf8()
                           .constData()))
    GuiFocusView(GUI_GRAPH);
}

void CPUDisassembly::analyzeModuleSlot() {
  DbgCmdExecDirect(QString("reanamod %1")
                       .arg(ToPtrString(rvaToVa(getSelectionStart())))
                       .toUtf8()
                       .constData());
}

void CPUDisassembly::createThreadSlot() {
  duint addr = rvaToVa(getSelectionStart());
  if (DbgFunctions()->IsDepEnabled() &&
      !DbgFunctions()->MemIsCodePage(addr, false)) {
    QMessageBox msg(QMessageBox::Warning,
                    tr("Current address is not executable"),
                    tr("Creating new thread here may result in crash. Do you "
                       "really want to continue?"),
                    QMessageBox::Yes | QMessageBox::No, this);
    msg.setWindowIcon(DIcon("compile-warning.png"));
    msg.setParent(this, Qt::Dialog);
    msg.setWindowFlags(msg.windowFlags() & (~Qt::WindowContextHelpButtonHint));
    if (msg.exec() == QMessageBox::No) return;
  }
  WordEditDialog argWindow(this);
  argWindow.setup(tr("Argument for the new thread"), 0, sizeof(duint));
  if (argWindow.exec() != QDialog::Accepted) return;
  DbgCmdExec(QString("createthread %1, %2")
                 .arg(ToPtrString(addr))
                 .arg(ToPtrString(argWindow.getVal())));
}

void CPUDisassembly::copyTokenTextSlot() {
  Bridge::CopyToClipboard(mHighlightToken.text);
}

void CPUDisassembly::copyTokenValueSlot() {
  QString text;
  if (getTokenValueText(text)) Bridge::CopyToClipboard(text);
}

bool CPUDisassembly::getTokenValueText(QString& text) {
  if (mHighlightToken.type <= ZydisTokenizer::TokenType::MnemonicUnusual)
    return false;
  duint value = mHighlightToken.value.value;
  if (!mHighlightToken.value.size &&
      !DbgFunctions()->ValFromString(mHighlightToken.text.toUtf8().constData(),
                                     &value))
    return false;
  text = ToHexString(value);
  return true;
}

void CPUDisassembly::followInMemoryMapSlot() {
  DbgCmdExec(QString("memmapdump %1")
                 .arg(ToHexString(rvaToVa(getInitialSelection()))));
}

void CPUDisassembly::downloadCurrentSymbolsSlot() {
  char module[MAX_MODULE_SIZE] = "";
  if (DbgGetModuleAt(rvaToVa(getInitialSelection()), module))
    DbgCmdExec(QString("symdownload \"%0\"").arg(module));
}

void CPUDisassembly::ActionTraceRecordToggleRunTraceSlot() {
  if (!DbgIsDebugging()) return;
  if (DbgValFromString("tr.runtraceenabled()") == 1)
    DbgCmdExec("StopRunTrace");
  else {
    QString defaultFileName;
    char moduleName[MAX_MODULE_SIZE];
    QDateTime currentTime = QDateTime::currentDateTime();
    duint defaultModule = DbgValFromString("mod.main()");
    if (DbgFunctions()->ModNameFromAddr(defaultModule, moduleName, false)) {
      defaultFileName = QString::fromUtf8(moduleName);
    }
    defaultFileName +=
        "-" + QLocale(QString(currentLocale)).toString(currentTime.date()) +
        " " + currentTime.time().toString("hh-mm-ss") +
        ArchValue(".trace32", ".trace64");
    BrowseDialog browse(this, tr("Select stored file"),
                        tr("Store run trace to the following file"),
                        tr("Run trace files (*.%1);;All files (*.*)")
                            .arg(ArchValue("trace32", "trace64")),
                        QCoreApplication::applicationDirPath() +
                            QDir::separator() + "db" + QDir::separator() +
                            defaultFileName,
                        true);
    if (browse.exec() == QDialog::Accepted) {
      if (browse.path.contains(QChar('"')) || browse.path.contains(QChar('\'')))
        SimpleErrorBox(this, tr("Error"),
                       tr("File name contains invalid character."));
      else
        DbgCmdExec(QString("StartRunTrace \"%1\"").arg(browse.path));
    }
  }
}

void CPUDisassembly::refreshPage() {
  // no need refresh for filebuffer cache
  if (mManaDB->opcodes_cache) {
    return;
  }
  DbgCmdExec(QString("refresh %1").arg(ToPtrString(getSelectedVa())));
}

void CPUDisassembly::createPageModuleSlot() {
  DbgGlobal::inst()->createMemModule(getSelectedVa());
  DbgCmdExec(QString("disasm %1").arg(ToPtrString(getSelectedVa())));
}
